﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;
using DeepEarth.Core.Utilities;

#if WINDOWS_PHONE
// Using our own Tuple class while a standard is unavailable
using DeepEarth.Core.Utilities.WP7;
#endif

namespace DeepEarth.Core.Data
{
    public class GeoItemViewModel : INotifyPropertyChanged
    {
        public bool IsPlaceHolder { get; set; }

        object data;
        public object Data
        {
            get
            {
                return data;
            }
            set
            {
                data = value;

                // if the data is atomic set the Atomic parent to this.
                // When a child is added to this view model, it will inherit
                // atomic parent. That way all descendents will have a reference to this
                // view model.
                if (GeoItemViewModelHelper.IsEntityAtomic(value))
                {
                    AtomicParent = this;
                }

                if (data is IItemsContainer)
                {
                    var container = data as IItemsContainer;
                    ChildItemsSource = container.Children;
                }
                else if (data is IEnumerable)
                {
                    ChildItemsSource = data as IEnumerable;
                }

                if (data is IMultiLocation)
                {
                    var ml = data as IMultiLocation;
                    Locations = ml.Locations;

                    Position = ml.Position ?? BoundingBox.Center;

                    if (ml.Locations is INotifyCollectionChanged)
                    {
                        var observableCollection = ml.Locations as INotifyCollectionChanged;

                        observableCollection.CollectionChanged += (o, e) =>
                        {
                            Locations = ml.Locations;
                            Position = ml.Position ?? BoundingBox.Center;
                        };
                    }
                }
                else if (data is ILocatable)
                {
                    var locatable = data as ILocatable;
                    Position = locatable.Position;

                    // handle location changes
                    if (data is INotifyPropertyChanged)
                    {
                        INotifyPropertyChanged inpc = data as INotifyPropertyChanged;
                        inpc.PropertyChanged += (o, e) =>
                        {
                            if (e.PropertyName == "Position")
                            {
                                this.Position = locatable.Position;
                            }
                        };
                    }
                }

                // Do this last
                // If the user has provided their own placeholder then this.Children should have been populated by now
                if (data is ILazyLoad)
                {
                    // If lazy load and not atomic, put a placeholder in the children collection - helps with TreeView controls
                    if (!GeoItemViewModelHelper.IsEntityAtomic(data))
                    {
                        var lazy = data as ILazyLoad;
                        if (lazy.LoadState != LazyLoadState.Loaded)
                        {
                            if (Children.Count == 0)
                            {
                                var placeHolder = new GeoItemViewModel { IsPlaceHolder = true, data = new object() };
                                Children.Add(placeHolder);
                            }
                        }
                    }
                    else
                    {
                        // Atomic and lazy load - set activity to false by default
                        IsActive = false;
                    }
                }

            }
        }

        bool isSelected;
        public bool IsSelected
        {
            get { return isSelected; }
            set
            {
                if (value && HierarchyActive())
                {
                    ExecuteSelectedCommand();
                }

                isSelected = value;
                NotifyPropertyChanged("IsSelected");
            }
        }

        bool HierarchyActive()
        {
            if (!IsActive)
                return false;

            var ancestor = Parent;

            while (ancestor != null)
            {
                if (!ancestor.IsActive)
                {
                    return false;
                }

                ancestor = ancestor.Parent;
            }

            return true;
        }

        protected virtual void ExecuteSelectedCommand()
        {
            if (SelectedCommand != null && SelectedCommand.CanExecute(this))
            {
                // If this view model is part of an atomic hierarchy, then if selected, the atomic object should be selected, not the
                // particular sub object that is excapsulated by this view model.
                // Unless it is overriden by an AlwaysSelectableAttribute
                var payload = Data;

                if (AtomicParent != null)
                    payload = AtomicParent.Data;

                SelectedCommand.Execute(new List<object> { payload });
            }

            if (Data != null)
            {
                var selectable = Data as ISelectable;

                if (selectable != null && selectable.SelectionCommand != null)
                {
                    selectable.SelectionCommand.Execute(selectable);
                }
            }
        }

        bool isExpanded;
        public bool IsExpanded
        {
            get
            {
                return isExpanded;
            }
            set
            {
                if (isExpanded != value)
                {
                    isExpanded = value;
                    NotifyPropertyChanged("IsExpanded");

                    if (value && Data is ILazyLoad)
                    {
                        LoadLazy();
                    }
                }
            }
        }

        void LoadLazy()
        {
            var lazy = Data as ILazyLoad;

            if (lazy != null && lazy.LoadState == LazyLoadState.NotLoaded)
            {
                lazy.Load();
            }
        }

        ICommand activationChangedCommand;
        public ICommand ActivationChangedCommand
        {
            get
            {
                if (activationChangedCommand == null && Parent != null)
                {
                    activationChangedCommand = Parent.ActivationChangedCommand;
                }
                return activationChangedCommand;
            }
            set
            {
                if (value != activationChangedCommand)
                {
                    activationChangedCommand = value;
                    NotifyPropertyChanged("ActivationChangedCommand");
                }
            }
        }

        bool isActive = true;

        public virtual bool IsActive
        {
            get { return isActive; }
            set
            {               
                // Even if already active, there will be changes to children
                if (value)
                {
                    if (Parent != null)
                    {
                        Parent.ChildActivated(this);
                    }

                    GeoItemViewModel ancestor = Parent;

                    while (ancestor != null)
                    {
                        ancestor.SetIsActiveInIsolation(true);
                        ancestor = ancestor.Parent;
                    }

                    IItemsContainer itemsContainer = this.Data as IItemsContainer;

                    if (itemsContainer != null)
                    {
                        if ((itemsContainer.ActivationCascade & ActivationCascadeMode.Activate) == ActivationCascadeMode.Activate)
                        {
                            foreach (GeoItemViewModel child in Children)
                            {
                                child.IsActive = true;
                            }
                        }
                    }
                    // the data is some collection - it doesn't implement IItemsCollection so can't control what happens when its
                    // activation changes
                    else if (this.Data is IEnumerable)
                    {
                        foreach (GeoItemViewModel child in Children)
                        {
                            child.IsActive = true;
                        }
                    }
                   
                    // Atomic entities cannot be expanded, so if it is visible, then lazyload if required.
                    if (data != null && GeoItemViewModelHelper.IsEntityAtomic(data))
                    {
                        LoadLazy();
                    }
                }
                else
                {
                    ShowDetails = false;

                    foreach (GeoItemViewModel child in Children)
                    {
                        child.IsActive = false;
                    }                
                }

                if (isActive != value)
                {
                    isActive = value;
                    NotifyPropertyChanged("IsActive");

                    if (ActivationChangedCommand != null)
                    {
                        ActivationChangedCommand.Execute(new Tuple<object, bool>(data, value));
                    }
                }
            }
        }

        // Used by the clusterer to control parent/child hierarchies. Assume that all features have loaded
        public void SetIsActiveInIsolation(bool value)
        {
            if (isActive != value)
            {
                isActive = value;
                NotifyPropertyChanged("IsActive");
                if (ActivationChangedCommand != null)
                    ActivationChangedCommand.Execute(new Tuple<object, bool>(data, value));
            }
        }

        void ChildActivated(GeoItemViewModel child)
        {
            IItemsContainer ic = this.Data as IItemsContainer;

            if (ic != null && (ic.ActivationCascade & ActivationCascadeMode.Radio) == ActivationCascadeMode.Radio)
            {
                foreach (var c in Children.Where(a => a != child))
                {
                    c.IsActive = false;
                }
            }
        }

        void ChildDetailsActivated(GeoItemViewModel child)
        {
            IItemsContainer ic = this.Data as IItemsContainer;

            if (ic != null && (ic.ActivationCascade & ActivationCascadeMode.Radio) == ActivationCascadeMode.Radio)
            {
                foreach (var c in Children.Where(a => a != child))
                {
                    c.ShowDetails = false;
                }
            }
        }

        ICommand showDetailsChangedCommand;
        public ICommand ShowDetailsChangedCommand
        {
            get
            {
                if (showDetailsChangedCommand == null && Parent != null)
                {
                    showDetailsChangedCommand = Parent.ShowDetailsChangedCommand;
                }
                return showDetailsChangedCommand;
            }
            set
            {
                if (value != showDetailsChangedCommand)
                {
                    showDetailsChangedCommand = value;
                    NotifyPropertyChanged("ShowDetailsChangedCommand");
                }
            }
        }

        bool showDetails;
        public virtual bool ShowDetails
        {
            get { return showDetails; }
            set
            {
                if (value == showDetails)
                    return;
                showDetails = value;
                NotifyPropertyChanged("ShowDetails");                


                if (Parent != null  && value)
                {
                    Parent.ChildDetailsActivated(this);
                }

                if (value && !IsActive)
                {
                    IsActive = true;
                }
                
                IItemsContainer itemsContainer = this.Data as IItemsContainer;

                ActivationCascadeMode testMode = value ? ActivationCascadeMode.Activate : ActivationCascadeMode.Deactivate;

                if (itemsContainer == null || (itemsContainer.ActivationCascade & testMode) == testMode)
                {
                    foreach (GeoItemViewModel child in Children)
                    {
                        child.ShowDetails = value;
                    }
                }

                if (ShowDetailsChangedCommand != null)
                    ShowDetailsChangedCommand.Execute(new Tuple<object, bool>(data, value));
            }
        }

        // If this view model is part of an atomic view model hierarchy, maintain a link to the atomic parent
        // Do this in lieu of searching for the atomic parent each time.
        public GeoItemViewModel AtomicParent { get; set; }

        ICommand navigateCommand;
        public ICommand NavigateCommand
        {
            get { return navigateCommand; }
            set
            {
                navigateCommand = value;

                if (Children != null)
                {
                    foreach (var vm in Children)
                    {
                        vm.NavigateCommand = value;
                    }
                }
            }
        }

        public void Navigate()
        {
            if (NavigateCommand != null)
            {
                NavigateCommand.Execute(Data);
            }
        }

        Location position;
        public virtual Location Position
        {
            get { return position; }
            set
            {
                position = value;
                NotifyPropertyChanged("Position");
            }
        }

        IEnumerable<Location> locations;
        public IEnumerable<Location> Locations
        {
            get { return locations; }
            set
            {
                locations = value;
                NotifyPropertyChanged("Locations");

                BoundingBox = new LocationRectangle(value);
            }
        }

        LocationRectangle boundingBox;
        public virtual LocationRectangle BoundingBox
        {
            get
            {
                return boundingBox;
            }
            private set
            {
                boundingBox = value;
                NotifyPropertyChanged("BoundingBox");
            }
        }

        ICommand selectedCommand;
        public ICommand SelectedCommand
        {
            get
            {
                if (selectedCommand == null && Parent != null)
                {
                    selectedCommand = Parent.SelectedCommand;
                }
                return selectedCommand;
            }
            set
            {
                if (value != selectedCommand)
                {
                    selectedCommand = value;
                    NotifyPropertyChanged("SelectedCommand");
                }
            }
        }

        #region view model hierarchy

        private GeoItemViewModel parent;
        public GeoItemViewModel Parent
        {
            get
            {
                return parent;
            }
            set
            {
                if (parent != value)
                {
                    parent = value;
                    NotifyPropertyChanged("Parent");
                }                
            }
        }

        // If the Data wrapped by this "view model" has children, which should also be rendered,
        // those children, when wrapped, will be added to the children viewmodel collection. The collection of actual
        // child items are referenced by the ChildItemsSource
        public ObservableCollection<GeoItemViewModel> Children { get; set; }

        WeakEventListener<GeoItemViewModel,INotifyCollectionChanged> childItemsCollectionChangedListener;

        private IEnumerable childItemsSource;
        public IEnumerable ChildItemsSource
        {
            get { return childItemsSource; }
            set
            {
                DetachCollectionListener();

                childItemsSource = value;

                if (Children != null)
                {
                    Children.Clear();
                }

                if (value != null)
                {
                    //// If the data model has the IsAtomic attribute, then don't create view models for children. (The data should be treated as if it were a single entity)
                    //// Similarly if it is an atomic ItemsContainer
                    //if (this.Data != null && (AtomicEntityAttribute.IsAtomic(this.Data) ||( this.Data is IItemsContainer && ((IItemsContainer)this.Data).IsAtomic)))
                    //{
                    //    SubscribeParentExpansion();
                    //    return;
                    //}

                    foreach (object o in childItemsSource)
                    {
                        Add(o);
                    }

                    var observableCollection = childItemsSource as INotifyCollectionChanged;
                    if (observableCollection != null)
                    {
                        childItemsCollectionChangedListener = null;
                        childItemsCollectionChangedListener = new WeakEventListener<GeoItemViewModel, INotifyCollectionChanged>(this, observableCollection)
                                {
                                    DetatchAction = (listener, source) => { source.CollectionChanged -= listener.OnEvent; },
                                    EventAction = (self, sender, args) => self.ItemsSource_CollectionChanged((NotifyCollectionChangedEventArgs) args)
                                };

                        observableCollection.CollectionChanged += childItemsCollectionChangedListener.OnEvent;
                    }
                }
            }
        }

        void ItemsSource_CollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add :
                case NotifyCollectionChangedAction.Remove :
                case NotifyCollectionChangedAction.Replace :
                    if (e.NewItems != null)
                    {
                        foreach (var item in e.NewItems)
                        {
                            Add(item);
                        }
                    }
                    if (e.OldItems != null)
                    {
                        foreach (var item in e.OldItems)
                        {
                            Remove(item);
                        }
                    }
                    break;

                case NotifyCollectionChangedAction.Reset :
                    Clear();

                    break;
            }
        }

        public bool Contains(object o)
        {
            return Children.Any(i => i.Data == o);
        }

        public GeoItemViewModel Add(object o)
        {
            var newVM = new GeoItemViewModel { Data = o, Parent = this, SelectedCommand = SelectedCommand, NavigateCommand = NavigateCommand };

            // if we're still atomic after the child has been added, then set the child's atomic parent to the current AtomicParent
            // i.e. we should let the underlying business object reevaluate if it still should be considered atomic
            if ((AtomicParent != null && AtomicParent != this) || (AtomicParent == this && GeoItemViewModelHelper.IsEntityAtomic(this.data)))
            {
                newVM.AtomicParent = AtomicParent;
            }

            if (this.IsActive)
            {
                IItemsContainer ic = this.Data as IItemsContainer;
                if (ic != null && (ic.ActivationCascade & ActivationCascadeMode.Activate) == ActivationCascadeMode.Activate)
                    newVM.IsActive = true; ;
            }

            Children.Add(newVM);
            
            if (o is IEnumerable)
            {
                newVM.ChildItemsSource = o as IEnumerable;
            }

            return newVM;
        }

        public void Remove(object o)
        {
            var item = Children.Where(vm => vm.Data == o).FirstOrDefault();
            if (item != null)
            {
                item.Clear();
                Children.Remove(item);
                item.ChildItemsSource = null;
            }
        }

        public void Clear()
        {
            Children.Clear();
        }

        void DetachCollectionListener()
        {
            if (childItemsSource is INotifyCollectionChanged && childItemsCollectionChangedListener != null)
            {
                childItemsCollectionChangedListener.DetatchAction(childItemsCollectionChangedListener, (INotifyCollectionChanged)childItemsSource);
            }

            childItemsSource = null;
        }
        #endregion

        public GeoItemViewModel()
        {
            Children = new ObservableCollection<GeoItemViewModel>();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}